fix(billing): validate priceId against allowlist and sanitize quantity at checkout endpoints (#2222) - #2237
Conversation
…y at checkout endpoints (CapSoftware#2222) - Add VALID_STRIPE_PLAN_PRICE_IDS and isValidStripePlanPriceId helper in @cap/utils - Validate priceId in guest-checkout, subscribe, and desktop subscribe endpoints - Sanitize quantity to positive bounded integers - Add unit tests verifying arbitrary priceId rejection and valid checkout creation Closes CapSoftware#2222
| if (environment) { | ||
| const envPlans = STRIPE_PLAN_IDS[environment]; | ||
| return priceId === envPlans.yearly || priceId === envPlans.monthly; | ||
| } |
There was a problem hiding this comment.
Cross-environment prices pass validation
The fallback allowlist accepts both test and live price IDs, and all three checkout endpoints call it without specifying the deployment environment. A production request containing a known development price ID therefore passes validation before Stripe rejects it against the live secret key, causing checkout to fail with a server error. Development and preview deployments have the inverse problem. Please validate against the deployment-specific price IDs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/utils/src/constants/plans.ts
Line: 33
Comment:
**Cross-environment prices pass validation**
The fallback allowlist accepts both test and live price IDs, and all three checkout endpoints call it without specifying the deployment environment. A production request containing a known development price ID therefore passes validation before Stripe rejects it against the live secret key, causing checkout to fail with a server error. Development and preview deployments have the inverse problem. Please validate against the deployment-specific price IDs.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…ment - Validate priceId against the deployment environment (production vs development) at all checkout endpoints - Support environment-specific allowlist checking in isValidStripePlanPriceId - Add unit test coverage for deployment-specific price ID validation
|
Updated in commit |
Resolves #2222
Linear: CAP-804
Summary
priceIdandquantitywere previously passed from untrusted client requests directly tostripe.checkout.sessions.createacrossguest-checkout,subscribe, and desktopPOST /subscribe. This permitted arbitrary Stripe price IDs to be submitted.Changes
packages/utils/src/constants/plans.ts):VALID_STRIPE_PLAN_PRICE_IDScontaining official Pro subscription plan price IDs.isValidStripePlanPriceId(priceId, environment?)validator.apps/web/app/api/settings/billing/guest-checkout/route.ts: ValidatespriceIdand boundsquantityto[1, 1000].apps/web/app/api/settings/billing/subscribe/route.ts: ValidatespriceIdand boundsquantityto[1, 1000].apps/web/app/api/desktop/[...route]/root.ts: Refined Zod schema onpriceIdviaisValidStripePlanPriceId.apps/web/__tests__/unit/subscribe-checkout.test.tsverifying allowlist enforcement.apps/web/__tests__/unit/mobile-checkout.test.tsverifying rejection of arbitrary price IDs with HTTP 400.Verification
vitest run __tests__/unit/subscribe-checkout.test.ts __tests__/unit/mobile-checkout.test.ts: 11/11 passedGreptile Summary
This PR adds a shared Stripe Pro-price allowlist, applies it to guest, authenticated web, and desktop checkout endpoints, normalizes web checkout quantities, and adds allowlist-focused unit coverage.
Confidence Score: 4/5
The PR should not merge until checkout validation is restricted to the active Stripe environment; the guest analytics discrepancy is lower-impact but should also be corrected.
Known opposite-environment price IDs pass all three new validators and then fail during Stripe session creation, so the intended input validation remains incomplete and can produce failed checkouts and server errors. Guest checkout additionally records quantities that can differ from those actually submitted to Stripe.
Files Needing Attention: packages/utils/src/constants/plans.ts; apps/web/app/api/settings/billing/guest-checkout/route.ts; apps/web/app/api/settings/billing/subscribe/route.ts; apps/web/app/api/desktop/[...route]/root.ts
Important Files Changed
Comments Outside Diff (1)
apps/web/app/api/settings/billing/guest-checkout/route.ts, line 57 (link)Stripe receives
safeQuantity, but theguest_checkout_startedevent recordsquantity || 1. For inputs such as-5,1.5,"abc", or2000, billing uses 1 while analytics records the invalid raw value. This makes checkout telemetry disagree with the session that was actually created.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix(billing): validate priceId against a..." | Re-trigger Greptile